package com.gengzp.common.interceptor;

import com.gengzp.common.context.MyBatisContextHolder;
import com.gengzp.common.enums.YesNoEnum;
import com.gengzp.common.exception.BusinessException;
import com.gengzp.common.model.generic.entity.GenericEntity;
import com.gengzp.common.model.pagination.req.PaginationReq;
import com.gengzp.common.utils.JsonUtils;
import com.gengzp.common.utils.SnowFlakeIdUtils;
import com.gengzp.permission.utils.PermissionUtils;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * MyBatis 增强拦截器
 * 核心功能：
 * 1. 分页查询自动处理（拼接分页SQL、计算总条数）- prepare阶段执行
 * 2. 写操作（INSERT/UPDATE）字段自动填充 - prepare阶段执行
 * 3. SQL执行异常统一捕获 - query/update阶段执行
 * 4. 可开关的SQL执行日志打印（统一格式：SQL语句 + 有序参数列表）- query/update阶段执行
 *
 * @author gengzp
 * @date 2025/9/8 14:39
 */
@Component
@Intercepts({
        // 1. prepare阶段：处理SQL预编译、分页SQL拼接、字段自动填充
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        // 2. parameterize阶段：绑定参数到Statement
        @Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class}),
        // 3. query阶段：处理查询执行、异常捕获、日志打印
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        // 4. update阶段：处理更新执行、异常捕获、日志打印
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
public class MyBatisInterceptor implements Interceptor {

    // 日志记录器
    private static final Logger logger = LoggerFactory.getLogger(MyBatisInterceptor.class);

    // 常量定义：SQL关键字（统一管理，避免魔法值）
    private static final String SQL_KEYWORD_SELECT = "select";
    private static final String SQL_KEYWORD_INSERT = "insert";
    private static final String SQL_KEYWORD_UPDATE = "update";
    private static final String SQL_KEYWORD_GROUP_BY = "group by";
    private static final String SQL_LIMIT_SUFFIX = " limit %d , %d";
    private static final String COUNT_SQL_TEMPLATE = "select count(*) from ( %s ) as tmp";

    // 常量定义：反射方法缓存（减少重复反射开销）
    private static final Map<Class<?>, Map<String, Method>> REFLECT_METHOD_CACHE = new ConcurrentHashMap<>();

    // 常量定义：Map参数中集合的常见Key（MyBatis默认封装的集合Key）
    private static final List<String> MAP_COLLECTION_KEYS = Arrays.asList("list", "collection", "array");

    /**
     * 拦截器核心执行方法：根据目标方法名分发处理逻辑
     *
     * @param invocation 拦截调用对象（包含目标方法、参数等信息）
     * @return 目标方法执行结果
     */
    @Override
    public Object intercept(Invocation invocation) throws Exception {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        String targetMethodName = invocation.getMethod().getName();

        // 分发到对应阶段的处理逻辑
        return switch (targetMethodName) {
            // 预编译阶段
            case "prepare" -> handlePreparePhase(invocation, metaObject, boundSql);
            // 参数绑定阶段
            case "parameterize" -> handleParameterizePhase(invocation, boundSql);
            // 执行阶段
            case "query", "update" -> handleExecutePhase(invocation, boundSql, targetMethodName);
            default -> invocation.proceed();
        };
    }

    // ------------------------------ prepare阶段处理逻辑 ------------------------------

    /**
     * 处理prepare阶段：SQL预编译、分页SQL拼接、字段自动填充
     */
    private Object handlePreparePhase(Invocation invocation, MetaObject metaObject, BoundSql boundSql) {
        Object parameter = boundSql.getParameterObject();
        String formattedSql = formatSql(boundSql.getSql());
        String paramLog = getSafeParamList(boundSql);

        try {
            // 无参数时直接执行预编译
            if (Objects.isNull(parameter)) {
                return invocation.proceed();
            }

            // 1. 分页查询处理：拼接分页SQL、计算总条数
            if (isPaginationQuery(boundSql, parameter)) {
                PaginationReq paginationParam = (PaginationReq) parameter;
                // 计算总条数（执行count查询）
                int totalCount = calculateTotalCount(invocation, metaObject, boundSql);
                int totalPages = totalCount == 0 ? 0 : (totalCount / paginationParam.getPageSize()) + (totalCount % paginationParam.getPageSize() == 0 ? 0 : 1);
                // 拼接分页SQL并设置到BoundSql
                int offset = (paginationParam.getPageNum() - 1) * paginationParam.getPageSize();
                String paginationSql = boundSql.getSql() + String.format(SQL_LIMIT_SUFFIX, offset, paginationParam.getPageSize());
                metaObject.setValue("delegate.boundSql.sql", paginationSql);
                // 设置分页结果到请求对象
                paginationParam.setTotal(totalCount);
                paginationParam.setPageCount(totalPages);
            }

            // 2. 写操作字段自动填充：INSERT/UPDATE时填充创建人/时间、修改人/时间等
            if (isWriteOperation(boundSql, parameter)) {
                Long currentUserId = getCurrentUserId();
                Date currentTime = new Date();
                boolean isInsert = boundSql.getSql().toLowerCase().startsWith(SQL_KEYWORD_INSERT);
                fillEntityFields(parameter, currentUserId, currentTime, isInsert);
            }

            // 执行prepare方法（创建Statement，完成SQL预编译和参数绑定）
            return invocation.proceed();
        } catch (Exception e) {
            // 捕获预编译阶段异常（参数绑定、SQL语法错误等），组装日志
            String errorMessage = getExceptionMessage(e);
            logger.error("""
                                        
                                        
                    ============================ 【SQL预编译失败】 ============================
                    【SQL语句】: {}
                    【SQL参数】: {}
                    【异常信息】: {}
                    =========================================================================
                                        
                    """, formattedSql, paramLog, errorMessage);
            // 包装为业务异常抛出
            throw BusinessException.get("sql预编译阶段执行失败：" + errorMessage);
        }
    }

    /**
     * 处理parameterize阶段：参数绑定到Statement，捕获参数绑定异常
     */
    private Object handleParameterizePhase(Invocation invocation, BoundSql boundSql) {
        String formattedSql = formatSql(boundSql.getSql());
        String paramLog = getSafeParamList(boundSql);

        try {
            // 执行参数绑定（调用StatementHandler.parameterize()）
            return invocation.proceed();
        } catch (Exception e) {
            String errorMessage = getExceptionMessage(e);
            logger.error("""
                                        
                                        
                    ============================ 【参数绑定失败】 ============================
                    【SQL语句】: {}
                    【实际绑定参数】: {}
                    【异常信息】: {}
                    =======================================================================
                                        
                    """, formattedSql, paramLog, errorMessage);
            throw BusinessException.get("sql参数绑定阶段执行失败：" + errorMessage);
        }
    }

    /**
     * 处理执行阶段：SQL执行、异常捕获、日志打印
     */
    private Object handleExecutePhase(Invocation invocation, BoundSql boundSql, String methodName) {
        long startTime = System.currentTimeMillis();
        String sqlType = "query".equals(methodName) ? "查询" : "更新";
        String formattedSql = formatSql(boundSql.getSql());
        String paramLog = getSafeParamList(boundSql);

        try {
            // 执行SQL（query/update方法，实际触发Statement.execute()）
            Object result = invocation.proceed();
            long costTime = System.currentTimeMillis() - startTime;

            // 打印执行日志（根据开关控制）
            if (MyBatisContextHolder.getPrintSqlLogFlag()) {
                printExecuteLog(sqlType, formattedSql, paramLog, costTime);
            }

            return result;
        } catch (Exception e) {
            String errorMessage = getExceptionMessage(e);
            // 捕获SQL执行异常，包装为业务异常抛出
            logger.error("""
                                        
                                        
                    ============================ 【SQL{}执行失败】 ============================
                    【SQL语句】: {}
                    【SQL参数】: {}
                    【异常信息】: {}
                    ==========================================================================
                                        
                    """, sqlType, formattedSql, paramLog, errorMessage);
            throw BusinessException.get("sql" + sqlType + "操作执行失败：" + errorMessage);
        }
    }

    // ------------------------------ 辅助方法（与原有逻辑一致，仅适配阶段调整） ------------------------------

    /**
     * 校验是否为分页查询
     */
    private boolean isPaginationQuery(BoundSql boundSql, Object parameter) {
        String sql = boundSql.getSql().toLowerCase().trim();
        return sql.startsWith(SQL_KEYWORD_SELECT) && parameter instanceof PaginationReq;
    }

    /**
     * 校验是否为写操作（INSERT/UPDATE）
     */
    private boolean isWriteOperation(BoundSql boundSql, Object parameter) {
        String sql = boundSql.getSql().toLowerCase().trim();
        if (!sql.startsWith(SQL_KEYWORD_INSERT) && !sql.startsWith(SQL_KEYWORD_UPDATE)) {
            return false;
        }
        if (parameter instanceof GenericEntity) {
            return true;
        }
        if (parameter instanceof Map<?, ?>) {
            return hasGenericEntityInMap((Map<?, ?>) parameter);
        }
        return false;
    }

    /**
     * 检查Map参数中是否包含GenericEntity类型
     */
    private boolean hasGenericEntityInMap(Map<?, ?> paramMap) {
        for (Object value : paramMap.values()) {
            if (value instanceof GenericEntity) {
                return true;
            }
            if (value instanceof List<?> list && !list.isEmpty()) {
                return list.get(0) instanceof GenericEntity;
            }
        }
        return false;
    }

    private int calculateTotalCount(Invocation invocation, MetaObject metaObject, BoundSql boundSql) {
        int totalCount = 0;
        String originalSql = boundSql.getSql();
        Connection connection = (Connection) invocation.getArgs()[0];

        try {
            // 1. 处理原始 SQL，移除 ORDER BY 及其后续内容（确保单引号闭合）
            String processedSql = removeOrderBy(originalSql);
            // 2. 移除 GROUP BY 及其后续内容（如果有）
            processedSql = removeGroupBy(processedSql);
            // 3. 生成 count SQL
            String countSql = String.format(COUNT_SQL_TEMPLATE, processedSql);
            metaObject.setValue("delegate.boundSql.sql", countSql);

            // 执行计数查询
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            totalCount = executeCountQuery(connection, mappedStatement, boundSql);
        } catch (Exception e) {
            logger.error("分页计数查询失败，原始SQL: {}", originalSql, e);
            String errorMsg = StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() :
                    (Objects.nonNull(e.getCause()) ? e.getCause().getMessage() : "未知错误");
            throw BusinessException.get("分页计数查询失败：" + errorMsg);
        } finally {
            metaObject.setValue("delegate.boundSql.sql", originalSql);
        }

        return totalCount;
    }

    /**
     * 移除 SQL 中的 ORDER BY 及其后续内容（确保单引号闭合）
     */
    private String removeOrderBy(String sql) {
        String lowerSql = sql.toLowerCase();
        int orderByIndex = lowerSql.indexOf("order by");
        if (orderByIndex == -1) {
            return sql; // 没有 ORDER BY 直接返回
        }

        // 检查 ORDER BY 之前的单引号是否闭合
        String beforeOrderBy = sql.substring(0, orderByIndex);
        int quoteCount = countQuotes(beforeOrderBy);
        if (quoteCount % 2 == 0) {
            // 单引号闭合，直接截取 ORDER BY 之前的内容
            return beforeOrderBy.trim();
        } else {
            // 单引号未闭合，说明 ORDER BY 在字符串中，不处理
            return sql;
        }
    }

    /**
     * 移除 SQL 中的 GROUP BY 及其后续内容（确保单引号闭合）
     */
    private String removeGroupBy(String sql) {
        String lowerSql = sql.toLowerCase();
        int groupByIndex = lowerSql.indexOf("group by");
        if (groupByIndex == -1) {
            return sql; // 没有 GROUP BY 直接返回
        }

        // 检查 GROUP BY 之前的单引号是否闭合
        String beforeGroupBy = sql.substring(0, groupByIndex);
        int quoteCount = countQuotes(beforeGroupBy);
        if (quoteCount % 2 == 0) {
            // 单引号闭合，直接截取 GROUP BY 之前的内容
            return beforeGroupBy.trim();
        } else {
            // 单引号未闭合，说明 GROUP BY 在字符串中，不处理
            return sql;
        }
    }

    /**
     * 统计字符串中的单引号数量（忽略转义的单引号，如 \'）
     */
    private int countQuotes(String str) {
        int count = 0;
        boolean escaped = false;
        for (char c : str.toCharArray()) {
            if (escaped) {
                escaped = false;
                continue;
            }
            if (c == '\\') {
                escaped = true;
            } else if (c == '\'') {
                count++;
            }
        }
        return count;
    }

    /**
     * 执行计数查询（获取总条数）
     */
    private int executeCountQuery(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) throws Exception {
        try (PreparedStatement ps = connection.prepareStatement(boundSql.getSql())) {
            DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), boundSql);
            parameterHandler.setParameters(ps);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return rs.getInt(1);
                }
            }
        }
        return 0;
    }

    /**
     * 获取当前登录用户ID
     */
    private Long getCurrentUserId() {
        return Optional.ofNullable(PermissionUtils.getCurrentUserId())
                .map(Long::valueOf)
                .orElse(null);
    }

    /**
     * 按参数类型执行实体字段填充
     */
    private void fillEntityFields(Object paramObject, Long currentUserId, Date currentTime, boolean isInsert) {
        if (paramObject instanceof Collection<?>) {
            ((Collection<?>) paramObject).forEach(entity -> doFillFields(entity, currentUserId, currentTime, isInsert));
        } else if (paramObject instanceof Map<?, ?>) {
            ((Map<?, ?>) paramObject).values().forEach(value -> {
                if (value instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof GenericEntity) {
                    list.forEach(entity -> doFillFields(entity, currentUserId, currentTime, isInsert));
                } else if (value instanceof GenericEntity) {
                    doFillFields(value, currentUserId, currentTime, isInsert);
                }
            });
        } else if (paramObject instanceof GenericEntity) {
            doFillFields(paramObject, currentUserId, currentTime, isInsert);
        }
    }

    /**
     * 具体的字段填充逻辑
     */
    private void doFillFields(Object entity, Long currentUserId, Date currentTime, boolean isInsert) {
        if (!(entity instanceof GenericEntity)) {
            logger.warn("实体[{}]未继承GenericEntity，跳过字段自动填充", entity.getClass().getName());
            return;
        }

        try {
            // 公共填充：修改人、修改时间
            fillCommonFields(entity, currentUserId, currentTime);
            // 插入操作专属填充
            if (isInsert) {
                fillInsertExclusiveFields((GenericEntity) entity, currentUserId, currentTime);
            }
        } catch (Exception e) {
            logger.error("字段自动填充失败，实体类型：{}，异常信息：{}", entity.getClass().getName(), e.getMessage(), e);
            throw BusinessException.get("字段自动填充失败：" + e.getMessage());
        }
    }

    /**
     * 填充公共字段（修改人、修改时间）
     */
    private void fillCommonFields(Object entity, Long currentUserId, Date currentTime) throws Exception {
        if (Objects.nonNull(currentUserId)) {
            Method setLastModifiedBy = getCachedMethod(entity.getClass(), "setLastModifiedBy", Long.class);
            setLastModifiedBy.invoke(entity, currentUserId);
        }
        Method setLastModifiedTime = getCachedMethod(entity.getClass(), "setLastModifiedTime", Date.class);
        setLastModifiedTime.invoke(entity, currentTime);
    }

    /**
     * 填充插入操作专属字段（id、创建人、创建时间、逻辑删除）
     */
    private void fillInsertExclusiveFields(GenericEntity entity, Long currentUserId, Date currentTime) throws Exception {
        if (Objects.isNull(entity.getId())) {
            Method setId = getCachedMethod(entity.getClass(), "setId", Long.class);
            setId.invoke(entity, SnowFlakeIdUtils.generateId());
        }
        if (Objects.nonNull(currentUserId)) {
            Method setCreatedBy = getCachedMethod(entity.getClass(), "setCreatedBy", Long.class);
            setCreatedBy.invoke(entity, currentUserId);
        }
        Method setCreatedTime = getCachedMethod(entity.getClass(), "setCreatedTime", Date.class);
        setCreatedTime.invoke(entity, currentTime);
        Method setIsDeleted = getCachedMethod(entity.getClass(), "setIsDeleted", Integer.class);
        setIsDeleted.invoke(entity, YesNoEnum.NO.getCode());
    }

    /**
     * 反射方法缓存工具
     */
    private Method getCachedMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        Map<String, Method> methodMap = REFLECT_METHOD_CACHE.computeIfAbsent(clazz, k -> new ConcurrentHashMap<>());
        String methodKey = methodName + "(" + Arrays.stream(parameterTypes).map(Class::getSimpleName).collect(Collectors.joining(",")) + ")";
        return methodMap.computeIfAbsent(methodKey, k -> {
            try {
                return clazz.getMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                throw BusinessException.get(String.format("实体[%s]缺少填充方法：%s", clazz.getSimpleName(), methodKey));
            }
        });
    }

    /**
     * 格式化SQL（去除多余换行和空格）
     */
    private String formatSql(String sql) {
        return sql.replaceAll("(\\r?\\n|\\r)", " ").replaceAll(" +", " ").trim();
    }

    /**
     * 获取安全的参数列表（避免反射异常）
     */
    private String getSafeParamList(BoundSql boundSql) {
        try {
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            Object paramObject = boundSql.getParameterObject();

            if (parameterMappings.isEmpty()) {
                return "[]";
            }

            List<String> paramValues = new ArrayList<>();

            for (ParameterMapping mapping : parameterMappings) {
                String property = mapping.getProperty();
                Object value = null;

                try {
                    // 安全获取参数值
                    if (paramObject != null) {
                        // 尝试通过MetaObject获取值
                        MetaObject metaObject = SystemMetaObject.forObject(paramObject);
                        if (metaObject.hasGetter(property)) {
                            value = metaObject.getValue(property);
                        } else if (paramObject instanceof Map<?, ?> && ((Map<?, ?>) paramObject).containsKey(property)) {
                            // 如果是Map类型，直接从Map获取
                            value = ((Map<?, ?>) paramObject).get(property);
                        } else if (paramObject instanceof String || paramObject instanceof Number || paramObject instanceof Boolean) {
                            // 如果参数对象是基本类型，直接使用参数对象
                            value = paramObject;
                        }
                    }

                    // 格式化参数值
                    String formattedValue = formatParameterValue(value);
                    paramValues.add(formattedValue);
                } catch (Exception e) {
                    // 如果获取参数值失败，使用占位符
                    logger.warn("获取参数值失败，属性: {}, 异常: {}", property, e.getMessage());
                    paramValues.add("?");
                }
            }

            return "[" + String.join(", ", paramValues) + "]";
        } catch (Exception e) {
            // 如果整个参数列表获取失败，返回安全的占位符
            logger.warn("获取参数列表失败，使用占位符: {}", e.getMessage());
            return "[?]";
        }
    }

    /**
     * 格式化参数值为字符串
     */
    private String formatParameterValue(Object value) {
        if (value == null) {
            return "null";
        } else if (value instanceof String) {
            // 对字符串参数进行转义，避免单引号问题
            String escapedValue = ((String) value).replace("'", "''"); // 将单引号转义为两个单引号
            return "'" + escapedValue + "'";
        } else if (value instanceof Number || value instanceof Boolean) {
            return value.toString();
        } else {
            // 对于复杂对象，安全地转换为JSON字符串
            try {
                return JsonUtils.toJsonString(value);
            } catch (Exception e) {
                // 如果JSON转换失败，使用对象的toString方法
                return value.toString();
            }
        }
    }

    /**
     * 获取异常信息的辅助方法，安全处理异常信息获取
     */
    private String getExceptionMessage(Exception e) {
        try {
            String message = e.getMessage();
            if (message == null) {
                if (e.getCause() != null) {
                    message = e.getCause().getMessage();
                }
            }
            if (message == null) {
                message = e.toString();
            }
            return message;
        } catch (Exception ex) {
            // 如果获取异常信息也失败，返回安全的默认信息
            return "获取异常信息失败: " + ex.getClass().getSimpleName();
        }
    }

    /**
     * 打印SQL执行日志
     */
    private void printExecuteLog(String sqlType, String formattedSql, String paramLog, long costTime) {
        logger.info("""
                 
                 
                 ============================ 【开始执行{}sql】 ============================\s
                【sql语句】:
                {}
                【sql参数】:
                {}
                 ============================ 【{}sql执行完成，耗时：{}ms】 ============================\s
                """, sqlType, formattedSql, paramLog, sqlType, costTime);
    }

    /**
     * 从Map参数中提取集合对象
     */
    public List<?> getCollectionFromMapParam(Object paramObject) {
        if (!(paramObject instanceof Map<?, ?> paramMap)) {
            return Lists.newArrayList();
        }
        for (String key : MAP_COLLECTION_KEYS) {
            Object value = paramMap.get(key);
            if (value instanceof Collection<?>) {
                return new ArrayList<>((Collection<?>) value);
            }
        }
        for (Object value : paramMap.values()) {
            if (value instanceof Collection<?>) {
                return new ArrayList<>((Collection<?>) value);
            }
        }
        return Lists.newArrayList();
    }
}



